package cn.mysdp.biz.domain;

import cn.mysdp.utils.JavaBeansUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName:
 * @Description:
 * @Author: SDP
 * @Date: 2020-06-01
 * @Version:1.0
 * Table:
 * Comment:
 *
 */
public class BaseExample {

    @JsonProperty("force_update_internal")
    @JSONField(name = "force_update_internal")
    private Integer forceUpdateInternal;

    public Integer getForceUpdateInternal() {
        return forceUpdateInternal;
    }

    public void setForceUpdateInternal(Integer _v) {
        forceUpdateInternal = _v;
    }

    @JsonProperty("limit_clause")
    @JSONField(name = "limit_clause")
    protected Integer limitClause;

    @JsonProperty("count_clause")
    @JSONField(name = "count_clause")
    protected Integer countClause;

    @JsonProperty("top_clause")
    @JSONField(name = "top_clause")
    protected Integer topClause;

    @JsonProperty("order_by_clause")
    @JSONField(name = "order_by_clause")
    protected String orderByClause;

    @JsonProperty("distinct")
    @JSONField(name = "distinct")
    protected boolean distinct;

    public void setLimitClause(Integer limitClause) {
        this.limitClause = limitClause;
    }

    public Integer getLimitClause() {
        return limitClause;
    }

    public void setCountClause(Integer countClause) {
        this.countClause = countClause;
    }

    public Integer getCountClause() {
        return countClause;
    }

    public void setTopClause(Integer topClause) {
        this.topClause = topClause;
    }

    public Integer getTopClause() {
        return topClause;
    }

    public void setOrderByClause(String orderByClause) {
        this.orderByClause = orderByClause;
    }

    public String getOrderByClause() {
        return orderByClause;
    }

    public void setDistinct(boolean distinct) {
        this.distinct = distinct;
    }

    public boolean isDistinct() {
        return distinct;
    }

    protected static int appendCriteriaOrderByItem(StringBuilder sb, String name, String value) throws Exception {
        if (value == null) {
            return 0;
        }
        if (sb.length() > 0) {
            sb.append(",");
        }
        value = value.trim();
        if ("desc/gbk".equals(value)) {
            sb.append("convert(");
            sb.append(name);
            sb.append(" using gbk) desc");
        } else if ("asc/gbk".equals(value)) {
            sb.append("convert(");
            sb.append(name);
            sb.append(" using gbk) asc");
        } else if ("desc".equals(value)) {
            sb.append(name);
            sb.append(" desc");
        } else if ("asc".equals(value) || "".equals(value)) {
            sb.append(name);
            sb.append(" asc");
        } else {
            throw new Exception("非法的order by类型:"+value);
        }
        return 1;
    }

    //           class     op         field   method
    protected static Map<Class, Map<String, Map<String, Method>>> clzMap = new HashMap<>();
    protected static Map<Class, Map<String, Map<String, Field>>> clzPropMap = new HashMap<>();

    protected static Map<Class, Map<String, Method>> clzMethodMap = new HashMap<>();
    public Method getMethodByCriteriaClass(Class clz, String key) throws Exception {
        String methodName = JavaBeansUtil.getCamelCaseString(key, false);
        Method method;
        synchronized (clzMethodMap) {
            Map<String, Method> methodMap;
            if (clzMethodMap.containsKey(clz)) {
                methodMap = clzMethodMap.get(clz);
            } else {
                methodMap = new HashMap<>();
                clzMethodMap.put(clz, methodMap);
            }
            if (methodMap.containsKey(key)) {
                method = methodMap.get(key);
            } else {
                method = JavaBeansUtil.getMethod(clz, methodName);
                if (method != null) {
                    methodMap.put(key, method);
                } else {
                    throw new Exception("not found method:"+key);
                }
            }
        }
        return method;
    }

    protected static Map<Class, Map<String, Field>> examplePropSetMap = new HashMap<>();
    public void fixExample(Class objClz, Class stringClz, Object criteria, JSONObject options) throws Exception {
        if (options == null) {
            return;
        }

        synchronized (examplePropSetMap) {
            Class clz = this.getClass();

            Map<String, Field> fieldMap = examplePropSetMap.get(clz);
            if (CollectionUtils.isEmpty(fieldMap)) {
                fieldMap = new HashMap<>();
                examplePropSetMap.put(clz, fieldMap);

                Map<String, Field> methodMap = new HashMap<>();
                while(clz != null) {
                    Field[] methods = clz.getDeclaredFields();
                    for(Field method: methods) {
                        if (Modifier.isStatic(method.getModifiers())) {
                            continue;
                        }
                        if (!method.isAnnotationPresent(JSONField.class)) {
                            continue;
                        }
                        JSONField jsonField = method.getAnnotation(JSONField.class);
                        if (!methodMap.containsKey(jsonField.name())) {
                            method.setAccessible(true);
                            methodMap.put(jsonField.name(), method);
                        } else {
                            System.err.println("exist property:"+jsonField.name()+" for "+this.getClass().getName());
                        }
                    }

                    Field[] fields = clz.getDeclaredFields();
                    for(Field field: fields) {
                        if (Modifier.isFinal(field.getModifiers())) {
                            continue;
                        }
                        if (!field.isAnnotationPresent(JSONField.class)) {
                            continue;
                        }
                        JSONField jsonField = field.getAnnotation(JSONField.class);
                        if (!fieldMap.containsKey(jsonField.name())) {
                            fieldMap.put(jsonField.name(), field);
                            field.setAccessible(true);
                        } else {
                            System.err.println("dup field property:"+jsonField.name()+" for "+this.getClass().getName());
                        }
                    }
                    clz = clz.getSuperclass();
                    if (clz == null) {
                        break;
                    }
                }
            }
        }

        Object intV = options.getInteger("limit");
        if (intV != null) {
            synchronized (examplePropSetMap.get(this.getClass())) {
                Field method = examplePropSetMap.get(this.getClass()).get("limit_clause");
                if (method != null) {
                    method.set(this, intV);
                } else {
                    throw new Exception("未定义方法：setLimitClause");
                }
            }
        }
        intV = options.get("count");
        if (intV != null) {
            synchronized (examplePropSetMap.get(this.getClass())) {
                Field method = examplePropSetMap.get(this.getClass()).get("count_clause");
                if (method != null) {
                    method.set(this, intV);
                } else {
                    throw new Exception("未定义方法：setCountClause");
                }
            }
        }
        intV = options.get("top");
        if (intV != null) {
            synchronized (examplePropSetMap.get(this.getClass())) {
                Field method = examplePropSetMap.get(this.getClass()).get("top_clause");
                if (method != null) {
                    method.set(this, intV);
                } else {
                    throw new Exception("未定义方法：setTop");
                }
            }
        }
        JSONObject jsonV = options.getJSONObject("set_null");
        if(jsonV != null) {
            Object obj = JSONObject.toJavaObject(jsonV, stringClz);
            synchronized (examplePropSetMap.get(this.getClass())) {
                Field method = examplePropSetMap.get(this.getClass()).get("set_null");
                if (method != null) {
                    method.set(this, obj);
                } else {
                    throw new Exception("未定义方法：setSetNull");
                }
            }
        }

        for(String key: options.keySet()) {
            if (key.startsWith("and_") && key.endsWith("_in")) {
                String[] vs = options.getString(key).split(",");
                List<String> vList = new ArrayList<>();
                for(String v: vs) {
                    if (StringUtils.hasText(v.trim())) {
                        vList.add(v.trim());
                    }
                }
                if (!CollectionUtils.isEmpty(vList)) {
                    Method method = getMethodByCriteriaClass(criteria.getClass(), key);
                    method.invoke(criteria, vList);
                }
            }
        }

        for(String op:new String[]{"in", "not in"}) {
            if(options.containsKey(op)) {
                JSONObject jsonObject;
                jsonObject = options.getJSONObject(op);
                for(String key: jsonObject.keySet()) {
                    JSONArray jsonArray = jsonObject.getJSONArray(key);
                    if (jsonArray != null && jsonArray.size() > 0) {
                        List<Object> vList = new ArrayList<>();
                        for(int i = 0; i < jsonArray.size(); i++) {
                            vList.add(jsonArray.get(i));
                        }
                        key = "and_"+key+"_"+op.replace(" ", "_");
                        Method method = getMethodByCriteriaClass(criteria.getClass(), key);
                        method.invoke(criteria, vList);
                    }
                }
            }
        }

        if(options.containsKey("=")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject("="), objClz);
            fixExample("EqualTo", 1, obj, criteria);
        }
        if(options.containsKey(">")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject(">"), objClz);
            fixExample("GreaterThan", 1, obj, criteria);
        }
        if(options.containsKey(">=")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject(">="), objClz);
            fixExample("GreaterThanOrEqualTo", 1, obj, criteria);
        }
        if(options.containsKey("!null")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject("!null"), stringClz);
            fixExample("IsNotNull", 0, obj, criteria);
        }
        if(options.containsKey("null")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject("null"), stringClz);
            fixExample("IsNull", 0, obj, criteria);
        }
        if(options.containsKey("<")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject("<"), objClz);
            fixExample("LessThan", 1, obj, criteria);
        }
        if(options.containsKey("<=")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject("<="), objClz);
            fixExample("LessThanOrEqualTo", 1, obj, criteria);
        }
        if(options.containsKey("like")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject("like"), objClz);
            fixExample("Like", 1, obj, criteria);
        }
        if(options.containsKey("!=")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject("!="), objClz);
            fixExample("NotEqualTo", 1, obj, criteria);
        }
        if(options.containsKey("!like")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject("!like"), objClz);
            fixExample("NotLike", 1, obj, criteria);
        }
        if(options.containsKey("regexp")) {
            Object obj = JSONObject.toJavaObject(options.getJSONObject("regexp"), objClz);
            fixExample("Regexp", 1, obj, criteria);
        }
    }
    public void fixExample(String op, Integer paramCount, Object obj, Object criteria) throws Exception {
        Map<String, Map<String, Method>> opMap;
        Map<String, Map<String, Field>> propMap;
        synchronized (clzMap) {
            if (!clzMap.containsKey(this.getClass())) {
                clzMap.put(this.getClass(), new HashMap<>());
                clzPropMap.put(this.getClass(), new HashMap<>());
            }
            opMap = clzMap.get(this.getClass());
            propMap = clzPropMap.get(this.getClass());
        }
        synchronized (opMap) {
            Map<String, Field> fieldMap = propMap.get(op);
            if (fieldMap == null) {
                fieldMap = new HashMap<>();
                propMap.put(op, fieldMap);

                Class clz = obj.getClass();
                Map<String, Field> methodMap = new HashMap<>();
                while(true) {
                    Field[] methods = clz.getDeclaredFields();
                    for(Field method: methods) {
                        if (Modifier.isStatic(method.getModifiers())) {
                            continue;
                        }
                        if (Modifier.isFinal(method.getModifiers())) {
                            continue;
                        }
                        if (!method.isAnnotationPresent(JSONField.class)) {
                            continue;
                        }
                        JSONField jsonField = method.getAnnotation(JSONField.class);
                        if (!methodMap.containsKey(jsonField.name())) {
                            methodMap.put(jsonField.name(), method);
                        } else {
                            System.err.println("dup field 3:"+obj.getClass().getName());
                        }
                    }
//                    if (!clz.getSimpleName().endsWith("WithBLOBs")) {
//                        break;
//                    }
                    clz = clz.getSuperclass();
                    if (clz == null) {
                        break;
                    }
                }
                clz = obj.getClass();
                while(true) {
                    Field[] fields = clz.getDeclaredFields();
                    for(Field field: fields) {
                        if (Modifier.isFinal(field.getModifiers())) {
                            continue;
                        }
                        if (Modifier.isStatic(field.getModifiers())) {
                            continue;
                        }
                        if (!field.isAnnotationPresent(JSONField.class)) {
                            continue;
                        }
                        JSONField jsonField = field.getAnnotation(JSONField.class);
                        if (!fieldMap.containsKey(jsonField.name())) {
                            field.setAccessible(true);
                            fieldMap.put(jsonField.name(), field);
                        } else {
                            System.err.println("dup field 3:"+obj.getClass().getName());
                        }

                    }
//                    if (!clz.getSimpleName().endsWith("WithBLOBs")) {
//                        break;
//                    }
                    clz = clz.getSuperclass();
                    if (clz == null) {
                        break;
                    }
                }
            }

            Map<String, Method> fieldForAndMap = opMap.get(op);
            if (fieldForAndMap == null) {
                fieldForAndMap = new HashMap<>();
                opMap.put(op, fieldForAndMap);

                Class criteriaClass = criteria.getClass();
                while(criteriaClass != null && !"GeneratedCriteria".equals(criteriaClass.getSimpleName())) {
                    criteriaClass = criteriaClass.getSuperclass();
                }
                if (criteriaClass == null) {
                    throw new Exception("未继承自GeneratedCriteria:"+this.getClass().getName());
                }
                Method[] methods = criteriaClass.getDeclaredMethods();
                Map<String, Method> methodMap = new HashMap<>();
                for(Method method: methods) {
                    if (Modifier.isStatic(method.getModifiers())) {
                        continue;
                    }
                    String methodName = method.getName();
                    if (!methodName.startsWith("and")) {
                        continue;
                    }
                    if (methodMap.containsKey(methodName)) {
                        throw new Exception("方法名重名："+this.getClass().getName()+","+methodName);
                    }
                    methodMap.put(methodName, method);
                }
                for(String field: fieldMap.keySet()) {
                    Method method = methodMap.get("and"+ JavaBeansUtil.getCamelCaseString(field, true)+op);
                    if (method != null) {
                        method.setAccessible(true);
                        fieldForAndMap.put(field, method);
                    }
                }
            }
            for(String field: fieldMap.keySet()) {
                Method method = fieldForAndMap.get(field);
                if (method != null) {
                    Object v = fieldMap.get(field).get(obj);
                    if (v != null) {
                        if (paramCount == 0) {
                            method.invoke(criteria);
                        } else if (paramCount == 1) {
                            method.invoke(criteria, v);
                        }
                    }
                }
            }
        }
    }

}
